home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 February: Tool Chest / Apple Developer CD Series Tool Chest February 1996 (Apple Computer)(1996).iso / Sample Code / AppsToGo / AppsToGo.docs / =How to write your app < prev   
Encoding:
Text File  |  1994-09-22  |  39.5 KB  |  962 lines  |  [TEXT/MPS ]

  1. The title of this document should have been:
  2.  
  3. How to write your application using AppWannabe (or Wannabe for short).
  4.  
  5. The above is too long for a file name, but obviously the title I used got your
  6. attention.  Now all I have to do is to hold it...
  7.  
  8. If you have built the libraries and sample applications, you may have already
  9. noted that Wannabe is actually an executable application.  Of course, it does
  10. absolutely nothing useful.  That's your job.
  11.  
  12. I am assuming that you have built the libraries for your compiler of choice
  13. and the sample starter application Wannabe.
  14.  
  15. DTS.Lib_strings (or DTSx.strings) contains only a single source file,
  16. StringUtils.c.  StringUtils.c contains some useful string functions.  It allows
  17. you to do various functions that are supplied by sprintf.  The amount of code
  18. you have to link in when you choose sprintf is quite large, however.  The
  19. supplied string functions allow you to easily format and parse string data
  20. without linking a large chunk of code.
  21.  
  22. It is suggested that you link the StringUtils executable into the code segment
  23. that contains main().  This will guarantee that the string functions are always
  24. resident in memory when you call them.  (MPW make files and project files are
  25. already set up this way.)  If the code isn't in ram when you make a string
  26. call, the loading of the code may cause memory to move or compress.  This
  27. is fine, unless you passed a pointer into an unlocked handle as a string
  28. pointer.  If you point into an unlocked handle, simply calling the function can
  29. move memory, unless it is guaranteed that the code is already in memory.  By
  30. linking it into the same code segment as main(), you guarantee that it is
  31. always in memory.  (All of the sample applications already do this.)
  32.  
  33.  
  34. Various libraries can be used without committing to the entire DTS.Lib
  35. application framework.  You can write your own application completely
  36. from scratch and then call any of the code in the above-mentioned files
  37. anytime you want.
  38.  
  39. Each of the above files has useful stand-alone code you will probably want to
  40. look at, even if you choose not to use the DTS.Lib application framework.
  41.  
  42. If you choose to use the DTS.Lib application framework, (from now on called
  43. DTS.framework), very much of the application development tasks are already done
  44. for you.
  45.  
  46. DTS.framework currently supports:
  47.     Multiple windows
  48.     Multiple document types
  49.     File I/O
  50.     Apple Events
  51.     Window/document scrolling
  52.     Hierarchical document architecture
  53.     Infinite undo (undos can optionally be saved with the document)
  54.     Floating palettes
  55.     Viewing window for displaying document objects
  56.     Other document-specific tasks, such as mouseDowns, keyDowns, cursors, etc.
  57.  
  58. What tasks DTS.framework doesn't directly supply can probably be found in the
  59. DTS utilities, so between the two, many of the aspects of Mac application
  60. development are already complete.
  61.  
  62.  
  63. So what is the minimum you have to know to get started?
  64.  
  65. Above, we discussed the various components of the DTS library code.  So now we
  66. need to know the easiest way to use this code.  The easiest way is to make a
  67. copy of the sample application Wannabe, and start adding code.  Consider Wannabe
  68. an application shell which already correctly uses the DTS.framework and
  69. utilities.  All that is missing is the code that is specific to your
  70. application.  Everything that is generic is already in there.
  71.  
  72. The best place to start explaining is how to manage documents.  We will start
  73. with a code snippet from Wannabe:
  74.  
  75. switch (menuItem) {
  76.     case kStdNew:
  77.         gDialogErr = NewDocumentWindow(&frHndl, gAppWindowType, true);
  78.         if (gDialogErr)
  79.             NewDocumentWindow(nil, 'ERR#', false);
  80.         break;
  81.         .
  82.         .
  83.         .
  84.  
  85.  
  86. This is right out of the file Menu.c  We get to this code by the user choosing
  87. "New" from the file menu.  The first thing we do is to try to create the new
  88. document.  This is done with the DTS.framework call NewDocument().
  89.  
  90. NewDocumentWindow() just bundles two framework function together, which are
  91. NewDocument() and DoNewWindow().  AppsToGo document-based windows are created
  92. by first creating the document, and then if that step is successful, the
  93. window for that document is created.
  94.  
  95. As stated above, DTS.framework can support multiple document types.  The type
  96. of the document we are trying to create is docFileType, which is an OSType.
  97. In Wannabe, docFileType is defined as follows:
  98.     #define docFileType      'DUMD'
  99.  
  100. 'DUMD' stands for DUMb Document.  I consider it dumb because when the document
  101. is saved, it is saved with no content.  The DTS.framework very carefully saves
  102. nothing.  You can then open nothing later.  You can print nothing, etc.
  103. Hopefully, you will remedy this situation.
  104.  
  105. The third parameter to NewDocumentWindow() tells NewDocumentWindow() that you
  106. want to create a document with a new "Untitled #" style of window title.  The
  107. true indicates to DTS.framework that you want this document to have an untitled
  108. number one greater then the last time that NewDocumentWindow() was called.
  109. No big deal, but I figured you would be curious at this point.
  110.  
  111. If NewDocumentWindow() succeeds, you're done.  If it fails, you probably want
  112. to pop up an alert stating such.  A single call to NewDocumentWindow() will
  113. do this for you, as well.
  114.  
  115. One of the fields in the frHndl that is initialized is the fileState.attributes
  116. field.  This field describes various window attributes if a window is opened for
  117. this document.  The default attributes are kept in the global gAppWindowAttr.
  118. If you don't like these attributes, you can change them in the frHndl after it
  119. is initialized and before you give the document a window.
  120.  
  121. The various values for the attribute field are:
  122.  
  123.     #define kwGrowIcon            0x00000001L
  124.     #define kwHScroll            0x00000002L
  125.     #define kwHScrollLessGrow    0x00000006L
  126.     #define kwVScroll            0x00000008L
  127.     #define kwVScrollLessGrow    0x00000018L
  128.     #define kwVisible            0x00000020L
  129.     #define kwOpenAtOldLoc        0x00000040L
  130.     #define kwDoFirstClick        0x00000080L
  131.     #define kwHideOnClose        0x00000100L
  132.     #define kwIsDocument        0
  133.     #define kwIsPalette            0x00000200L
  134.     #define kwIsModalDialog        0x00000400L
  135.     #define kwDefaultDocHeader    0x00000800L
  136.     #define kwHeaderIsResource    0x00001000L
  137.     #define kwRuntimeOnlyDoc    0x00002000L
  138.     #define kwAutoNew            0x00004000L
  139.     #define kwDefaultDocType    0x00008000L
  140.     #define kwColorMonitor        0x00010000L
  141.     #define kwSecondaryMonitor    0x00020000L
  142.     #define kwStaggerWindow        0x00040000L
  143.     #define kwCenterWindow        0x00080000L
  144.     #define kwSameMonitor        0x00100000L
  145.  
  146. So, if you want to create a window with document scrollbars, you just set a few
  147. bits in the attribute field, and you're done.  And yes, kwOpenAtOldLoc
  148. automatically opens the window wherever the user had it when it was closed,
  149. and yes it makes sure that it is at least partially visible on some monitor.
  150. You can find more information elsewhere.  (These bit-fields are setable via
  151. the AppsToGo application editor.  You do no have to set them via code.)
  152.  
  153. So what is really going on?
  154.  
  155. NewDocumentWindow() first calls NewDocument().  NewDocument() calls the function
  156. InitDocument(), which is yours (found in File.c).  Here you can check the OSType
  157. of the frHndl, and then make adjustments based on the various document types in
  158. your application.  To dereference to the attributes field for example, given an
  159. frHndl, do like so:  blahAttrs = (*frHndl)->fileState.attributes;
  160.  
  161. Other changes can be made to the frHndl at this point.  This is where you state
  162. that you don't want certain default behaviors for the document.  Note that you
  163. probably don't want to actually make these choices via code here.  These choices
  164. can generally be made with the AppsToGo application editor.
  165.  
  166.  
  167. Assuming that the frHndl got created successfully, the next step for
  168. NewDocumentWindow() is to create the window.  NewDocumentWindow() passes
  169. DoNewWindow() the successfully created (and possibly altered) frHndl.
  170. DoNewWindow() looks up various fields in the frHndl, and creates the window
  171. accordingly.
  172.  
  173.  
  174. The prototype for DoNewWindow is:
  175.  
  176. OSErr    DoNewWindow(FileRecHndl frHndl, WindowPtr *retWindow,
  177.                     WindowPtr relatedWindow, WindowPtr behind);
  178.  
  179. If DoNewWindow() successfully creates a window, the window is returned in
  180. retWindow, if retWindow is not nil.  Many times you don't really care what the
  181. window that was created is.  In this case, if it is successful, it is now the
  182. new top window.  If the user clicks on it, then you care.  Here you don't.
  183.  
  184. If DoNewWindow() was unsuccessful at creating the window, you now are left with
  185. a document, frHndl, that has no window.  If this occurs, you want to dispose of
  186. the document, and report an error.
  187.  
  188. That's all there is to it.  Oh, the remaining parameters to DoNewWindow():
  189.     relatedWindow:    Create the window on the same monitor that holds most of
  190.                     this window. If nil is passed in, create the window on the
  191.                     main monitor.
  192.     behind:            Create the window behind the window indicated.
  193.                     -1 puts it on top.
  194.  
  195.  
  196. Now that we understand document creation and window creation using
  197. DTS.framework, here's the rest of the story:
  198.  
  199.  
  200. As mentioned above, NewDocument() returns a handle.  This handle is your
  201. reference to the document, thus my favorite variable name for one os these
  202. things:  frHndl (file reference handle).  You don't have to worry about
  203. associating the frHndl with the window that was created for it.  DoNewWindow()
  204. stores the frHndl in the window's refcon field.  Also, you don't have to worry
  205. about which window belongs to a particular frHndl, as the window is stored in
  206. the frHndl, as well.  The document and its associated window both point to one
  207. another.  (You got one, you got the other -- cool.)
  208.  
  209.  
  210. Basically, everything that goes on with documents goes on inside the frHndl.
  211. The frHndl is a handle that holds the structure for the document.  Most of this
  212. structure looks like this:
  213.  
  214. typedef struct {
  215.     OSType                        sfType;
  216.     Boolean                        defaultDoc;
  217.     Movie                        movie;
  218.     short                        movieResID;
  219.     short                        movieFlags;
  220.     Boolean                        movieDataRefWasChanged;
  221.     Boolean                        docDirty;
  222.     long                        modNum;
  223.     long                        modTick;
  224.     Boolean                        readOnly;
  225.     short                        refNum;
  226.     short                        resRefNum;
  227.     FSSpec                        fss;
  228.     short                        windowID;
  229.     WindowPtr                    window;
  230.     PositionWndProcPtr            getDocWindow;
  231.     CalcFrameRgnProcPtr            calcFrameRgnProc;
  232.     ContentClickProcPtr            contentClickProc;
  233.     ContentKeyProcPtr            contentKeyProc;
  234.     DrawFrameProcPtr            drawFrameProc;
  235.     FreeDocumentProcPtr            freeDocumentProc;
  236.     FreeWindowProcPtr            freeWindowProc;
  237.     ImageProcPtr                imageProc;
  238.     InitContentProcPtr            initContentProc;
  239.     ReadDocumentProcPtr            readDocumentProc;
  240.     ReadDocumentHeaderProcPtr    readDocumentHeaderProc;
  241.     ResizeContentProcPtr        resizeContentProc;
  242.     ScrollFrameProcPtr            scrollFrameProc;
  243.     UndoFixupProcPtr            undoFixupProc;
  244.     WindowCursorProcPtr            windowCursorProc;
  245.     WriteDocumentProcPtr        writeDocumentProc;
  246.     WriteDocumentHeaderProcPtr    writeDocumentHeaderProc;
  247.     AdjustMenuItemsProcPtr        adjustMenuItemsProc;
  248.     DoMenuItemProcPtr            doMenuItemProc;
  249.     long                        attributes;  /* Here down is wndContent info. */
  250.     Rect                        windowSizeBounds;
  251.     ControlHandle                hScroll;
  252.     ControlHandle                vScroll;
  253.     short                        hScrollIndent;
  254.     short                        vScrollIndent;
  255.     short                        leftSidebar;
  256.     short                        topSidebar;
  257.     short                        hArrowVal;
  258.     short                        vArrowVal;
  259.     short                        hPageVal;
  260.     short                        vPageVal;
  261. } FileStateRec;
  262.  
  263.  
  264. Here's some explanation of some of the fields:
  265.  
  266. sfType:                    The type of the document you passed to NewDocument() is
  267.                         stored here.
  268.  
  269. defaultDoc:                Used by application framework to determine if View
  270.                         Hierarchy facility can be used to view the document.
  271.  
  272. movie:                    These 4 fields are used together.  If the document type
  273. movieResID:                used is of type 'MooV' for either NewDocument or
  274. movieFlags:                OpenDocument, then these fields are used to keep track
  275. movieDataRefWasChanged:    of the movie information for the document.  In addition
  276.                         to this information, the meaning of the field refNum is
  277.                         extended somewhat.  Here's the deal:  Movie files are
  278.                         generally resource-fork-based, but this isn't always the
  279.                         case.  The movie file can be flattened and made to be
  280.                         data-fork-based.  This is so MS-DOS machines can handle
  281.                         the movie file.
  282.                         To be consistent with regular document files, the
  283.                         resRefNum returned by OpenMovieFile (which isn't always
  284.                         a resource refNum) is kept in the field refNum.
  285.                         Normally refNum represents a data fork, but in the case
  286.                         of movies, it may be either a data fork or resource fork
  287.                         reference number.  For regular documents, if you wish to
  288.                         use the resource fork, you use the calls UseDocResFile
  289.                         and CloseDocResFile.  These still work for movies,
  290.                         whether the movie is data-fork- or resource-fork-based.
  291.                         If the movie is resource fork based, then UseDocResFile
  292.                         simply copies the refNum field into the resRefNum field.
  293.                         This is the correct thing to do, as the resource is
  294.                         already opened for the movie.  CloseDocResFile looks at
  295.                         the refNum and resRefNum fields.  If they are the same,
  296.                         then it simply sets resRefNum to kInvalidRefNum.  This
  297.                         is all that is necessary to "close" the resource fork,
  298.                         as you really don't want it to close, since it was
  299.                         opened for the movie.  All of this behavior is simply
  300.                         to allow the various calls to do the appropriate thing
  301.                         when the document is a movie.  You should be able to 
  302.                         handle movie files just like regular document files.
  303.  
  304. docDirty:                Used by the application framework to determine if a
  305.                         "Save before closing" dialog should be displayed.  
  306.  
  307. modNum:                    This is incremented whenever you call SetDocDirty().
  308.                         Some applications find this information useful.
  309.                         Most don't care.
  310.  
  311. modTick:                Tick count of last modification.  (See modNum).
  312.  
  313. readOnly:                True if document is opened read-only.  DTS.framework
  314.                         sets this if the document that is selected by the user
  315.                         can't be openeded with read-write access.  DTS.framework
  316.                         asks the user if it is okay to open it read-only.
  317.  
  318. refNum:                    The file reference number (0 if it is a new document.)
  319.  
  320. resRefNum:                Reference number of the resource fork of the document
  321.                         file.  Unless you specifically use the resource fork,
  322.                         the document's resource fork isn't opened.
  323.  
  324. fss                        The FSSpec for the open file (if there is one open for
  325.                         this document.)
  326.  
  327. windowID:                The resource ID of the 'WIND' resource to be used when
  328.                         DoNewDocument() is called to give this document a
  329.                         window.  windowID is initialized to rWindow, which is
  330.                         defined to be 128.  If you want a different 'WIND'
  331.                         resource for this document, set this field after
  332.                         calling NewDocument(), and prior to calling
  333.                         DoNewWindow().
  334.  
  335. window:                    When you call DoNewWindow(), if it succeeds at creating
  336.                         a window, it places the window pointer here.
  337.  
  338. The next fields are the procedure pointers that determine the behavior of the
  339. document.  These fields are initialized to point to the following functions:
  340.  
  341. For documents:
  342.  
  343.     getDocWindow:                GetStaggeredWindow
  344.     calcFrameRgnProc:            CalcFrameRgn
  345.     contentClickProc:            ContentClick
  346.     contentKeyProc:                ContentKey
  347.     drawFrameProc:                DrawFrame
  348.     freeDocumentProc:            FreeDocument
  349.     freeWindowProc:                FreeWindow
  350.     imageProc:                    ImageDocument
  351.     initContentProc:            InitContent
  352.     readDocumentProc:            ReadDocument
  353.     readDocumentHeaderProc:        nil
  354.     resizeContentProc:            ResizeContent
  355.     scrollFrameProc:            ScrollFrame
  356.     undoFixupProc:                UndoFixup
  357.     windowCursorProc:            WindowCursor
  358.     writeDocumentProc:            WriteDocument
  359.     writeDocumentHeaderProc:    nil
  360.     adjustMenuItemsProc:        AdjustMenuItems
  361.     doMenuItemProc:                DoMenuItem
  362.  
  363.  
  364. For dialogs:
  365.  
  366.     getDocWindow:                GetStaggeredWindow
  367.     calcFrameRgnProc:            DialogCalcFrameRgn
  368.     contentClickProc:            DialogContentClick
  369.     contentKeyProc:                DialogContentKey
  370.     drawFrameProc:                DialogDrawFrame
  371.     freeDocumentProc:            DialogFreeDocument
  372.     freeWindowProc:                DialogFreeWindow
  373.     imageProc:                    DialogImageDocument
  374.     initContentProc:            DialogInitContent
  375.     readDocumentProc:            nil
  376.     readDocumentHeaderProc:        nil
  377.     resizeContentProc:            DialogResizeContent
  378.     scrollFrameProc:            DialogScrollFrame
  379.     undoFixupProc:                DialogUndoFixup
  380.     windowCursorProc:            DialogWindowCursor
  381.     writeDocumentProc:            nil
  382.     writeDocumentHeaderProc:    nil
  383.     adjustMenuItemsProc:        DialogAdjustMenuItems
  384.     doMenuItemProc:                DialogDoMenuItem
  385.  
  386.  
  387. For palettes:
  388.  
  389.     getDocWindow:                GetStaggeredWindow
  390.     calcFrameRgnProc:            PaletteCalcFrameRgn
  391.     contentClickProc:            PaletteContentClick
  392.     contentKeyProc:                PaletteContentKey
  393.     drawFrameProc:                PaletteDrawFrame
  394.     freeDocumentProc:            PaletteFreeDocument
  395.     freeWindowProc:                PaletteFreeWindow
  396.     imageProc:                    PaletteImageDocument
  397.     initContentProc:            PaletteInitContent
  398.     readDocumentProc:            nil
  399.     readDocumentHeaderProc:        nil
  400.     resizeContentProc:            PaletteResizeContent
  401.     scrollFrameProc:            PaletteScrollFrame
  402.     undoFixupProc:                PaletteUndoFixup
  403.     windowCursorProc:            PaletteWindowCursor
  404.     writeDocumentProc:            nil
  405.     writeDocumentHeaderProc:    nil
  406.     adjustMenuItemsProc:        AdjustMenuItems
  407.     doMenuItemProc:                DoMenuItem
  408.  
  409. Note that unless gAppWindowAttr is set for a dialog or palette document/window,
  410. all documents will be created with the first set of procPtrs.
  411.  
  412. (AppsToGo allows you to set a document/window as a dialog or palette, and if
  413. you are using AppsToGo for development, then gAppWindowAttr isn't used.  Instead
  414. the attributes for the document/window description set with AppsToGo are.
  415. AppsToGo allows you to indicate if a document/window should be a dialog
  416. or palette.)
  417.  
  418. With the exception of the function GetStaggeredWindow, all of the above
  419. functions are part of the application shell Wannabe.  GetStaggeredWindow is a
  420. function in the DTS utilities that opens a window "staggered" from existing
  421. windows.  This allows some of the window to be exposed so that the user can
  422. easily click on the window and bring it to the front.
  423.  
  424. All of the other functions are part of your application.  They get called by
  425. the application framework at appropriate times.  They don't get called at some
  426. magic moment.  They get called when your application calls DTS.framework to do
  427. something.  DTS.framework does the generic work. The application-specific work
  428. it can't do, and so it calls your application to do that part.
  429.  
  430. Even for something as supposedly application-specific as drawing the window
  431. content, you still will call DTS.framework to get the job started.  You will
  432. call the function DoImageDocument().  You pass DoImageDocument() a file
  433. reference handle (frHndl).  DoImageDocument() dereferences the frHndl and
  434. gets the value at the field imageProc.  If imageProc is nil, then there is
  435. no imaging procedure for this document, and DoImageDocument() just returns.
  436. If there is a procedure pointer stored in the field imageProc, then that
  437. code is called.
  438.  
  439. There is no particular magic happening here.  The DoImageDocument() code looks
  440. just as you would expect:
  441.  
  442. OSErr    DoImageDocument(FileRecHndl frHndl)
  443. {
  444.     ImageProcPtr    proc;
  445.     OSErr            err;
  446.  
  447.     err = noErr;
  448.     if (proc = (*frHndl)->fileState.imageProc) err = (*proc)(frHndl);
  449.     return(err);
  450. }
  451.  
  452. If there is an imaging procedure in the file reference, call it.  Otherwise
  453. it just returns noErr.
  454.  
  455. Initially imageProc has a value.  The initial value is ImageDocument.  The
  456. ImageDocument() function is located in the Wannabe file Window.c.  If your
  457. application only has a single document type, then just place your document
  458. imaging code inside the stub function ImageDocument().  It's that easy.
  459.  
  460. For the most part, all of the procedure pointers in the frHndl have a "Do"
  461. function.  For example:  If you want to draw the "frame" portion of a window,
  462. you wouldn't call DrawFrame().  You would call DoDrawFrame().  DoDrawFrame()
  463. will look up the procedure pointer, and if it is not nil, it will call it,
  464. just like DoImageDocument() does.
  465.  
  466.  
  467. So how exactly does a file reference handle get created?  Again, nothing
  468. magic, but there are a number of steps that need to be understood.
  469.  
  470.  
  471. The creation of an frHndl is invoked by the application.  The application
  472. calls either NewDocument() or OpenDocument(), discussed above.  OpenDocument()
  473. calls NewDocument(), so in either case, you are actually calling NewDocument().
  474.  
  475. I think the easiest way to explain what NewDocument() does is to show the code.
  476. The two key places are marked with •••.  The explanation follows.  Here's most
  477. of the code:
  478.  
  479.  
  480. OSErr    NewDocument(FileRecHndl *returnHndl, OSType sftype, Boolean incTitleNum)
  481. {
  482.     long                size;
  483.     FileRecHndl            frHndl;
  484.     FileRecPtr            frPtr;
  485.     Str255                untitled;
  486.     StringPtr            pstr;
  487.     OSErr                err;
  488.     short                i;
  489.     Movie                movie;
  490.     TreeObjHndl            wobj;
  491.     PositionWndProcPtr    windowPlacementProc;
  492.     static short        untitledCount;
  493.  
  494.     if (returnHndl)
  495.         *returnHndl = nil;
  496.  
  497.     if (!sftype) {
  498.         if (!returnHndl)
  499.             untitledCount = 0;        /* API trick to allow you to reset the document count. */
  500.         return(noErr);
  501.     }
  502.  
  503.     err  = memFullErr;                /* Assume that we will fail. */
  504.  
  505. •••    size = InitDocumentSize(sftype);
  506.         /* Call the application and ask it how big the frHndl should be for
  507.         ** this document type.  We can't know, so we'll ask. */
  508.  
  509.     if (frHndl = (FileRecHndl)NewHandleClear(size)) {
  510.         /* Create (or try to) the frHndl, initialized to all 0's */
  511.  
  512.         if (returnHndl)
  513.             *returnHndl = frHndl;
  514.  
  515.         .
  516.         .
  517.         . This varies, depending on if you are using AppsToGo.
  518.         .
  519.         .
  520.  
  521. /* Below we fill in the various fields with defaults for the frHndl. */
  522.  
  523.         (*frHndl)->fileState.modNum = GetModNum();
  524.             /* In case GetModNum gets moved to another code segment, set this value
  525.             ** prior to dereferencing frHndl into frPtr. */
  526.  
  527.         frPtr = *frHndl;
  528.         frPtr->fileState.sfType                  = sftype;
  529.         frPtr->fileState.modTick                 = TickCount();
  530.         frPtr->fileState.refNum                  = kInvalRefNum;
  531.         frPtr->fileState.resRefNum               = kInvalRefNum;
  532.         frPtr->fileState.fss.vRefNum             = kInvalVRefNum;
  533.         frPtr->fileState.windowID                = rWindow;
  534.             /* The above sets the fileState constants for the document.  Note
  535.             ** that we use a default 'WIND' ID for the expected window resource.
  536.             ** This can be changed later, if the default isn't good enough. */
  537.  
  538.         if (wobj) {
  539.             frPtr->fileState.windowID      = mDerefWFMT(wobj)->windowID;
  540.             frPtr->fileState.attributes    = mDerefWFMT(wobj)->attributes;
  541.             frPtr->fileState.hScrollIndent = mDerefWFMT(wobj)->hScrollIndent;
  542.             frPtr->fileState.vScrollIndent = mDerefWFMT(wobj)->vScrollIndent;
  543.             frPtr->fileState.leftSidebar   = mDerefWFMT(wobj)->leftSidebar;
  544.             frPtr->fileState.topSidebar    = mDerefWFMT(wobj)->topSidebar;
  545.                 /* Set window attributes as described in resource. */
  546.         }
  547.         else
  548.             frPtr->fileState.attributes = gAppWindowAttr;
  549.                 /* Set window attributes for the main document type. If the document
  550.                 ** is not the main type, then the application's InitDocument function
  551.                 ** will have to change it. */
  552.  
  553.         frPtr->fileState.getDocWindow        = windowPlacementProc;
  554.         frPtr->fileState.adjustMenuItemsProc = AdjustMenuItems;
  555.         frPtr->fileState.doMenuItemProc      = DoMenuItem;
  556.         switch (frPtr->fileState.attributes & (kwIsPalette | kwIsModalDialog)) {
  557.             case kwIsPalette:
  558.                 frPtr->fileState.calcFrameRgnProc  = PaletteCalcFrameRgn;
  559.                 frPtr->fileState.contentClickProc  = PaletteContentClick;
  560.                 frPtr->fileState.contentKeyProc    = PaletteContentKey;
  561.                 frPtr->fileState.drawFrameProc     = PaletteDrawFrame;
  562.                 frPtr->fileState.freeDocumentProc  = PaletteFreeDocument;
  563.                 frPtr->fileState.freeWindowProc    = PaletteFreeWindow;
  564.                 frPtr->fileState.imageProc         = PaletteImageDocument;
  565.                 frPtr->fileState.initContentProc   = PaletteInitContent;
  566.                 frPtr->fileState.readDocumentProc  = nil;
  567.                 frPtr->fileState.resizeContentProc = PaletteResizeContent;
  568.                 frPtr->fileState.scrollFrameProc   = PaletteScrollFrame;
  569.                 frPtr->fileState.undoFixupProc     = PaletteUndoFixup;
  570.                 frPtr->fileState.windowCursorProc  = PaletteWindowCursor;
  571.                 frPtr->fileState.writeDocumentProc = nil;
  572.                 break;
  573.             case kwIsModalDialog:
  574.                 frPtr->fileState.calcFrameRgnProc    = DialogCalcFrameRgn;
  575.                 frPtr->fileState.contentClickProc    = DialogContentClick;
  576.                 frPtr->fileState.contentKeyProc      = DialogContentKey;
  577.                 frPtr->fileState.drawFrameProc       = DialogDrawFrame;
  578.                 frPtr->fileState.freeDocumentProc    = DialogFreeDocument;
  579.                 frPtr->fileState.freeWindowProc      = DialogFreeWindow;
  580.                 frPtr->fileState.imageProc           = DialogImageDocument;
  581.                 frPtr->fileState.initContentProc     = DialogInitContent;
  582.                 frPtr->fileState.readDocumentProc    = nil;
  583.                 frPtr->fileState.resizeContentProc   = DialogResizeContent;
  584.                 frPtr->fileState.scrollFrameProc     = DialogScrollFrame;
  585.                 frPtr->fileState.undoFixupProc       = DialogUndoFixup;
  586.                 frPtr->fileState.windowCursorProc    = DialogWindowCursor;
  587.                 frPtr->fileState.writeDocumentProc   = nil;
  588.                 frPtr->fileState.adjustMenuItemsProc = DialogAdjustMenuItems;
  589.                 frPtr->fileState.doMenuItemProc      = DialogDoMenuItem;
  590.                 break;
  591.             default:
  592.                 frPtr->fileState.calcFrameRgnProc  = CalcFrameRgn;
  593.                 frPtr->fileState.contentClickProc  = ContentClick;
  594.                 frPtr->fileState.contentKeyProc    = ContentKey;
  595.                 frPtr->fileState.drawFrameProc     = DrawFrame;
  596.                 frPtr->fileState.freeDocumentProc  = FreeDocument;
  597.                 frPtr->fileState.freeWindowProc    = FreeWindow;
  598.                 frPtr->fileState.imageProc         = ImageDocument;
  599.                 frPtr->fileState.initContentProc   = InitContent;
  600.                 frPtr->fileState.readDocumentProc  = ReadDocument;
  601.                 frPtr->fileState.resizeContentProc = ResizeContent;
  602.                 frPtr->fileState.scrollFrameProc   = ScrollFrame;
  603.                 frPtr->fileState.undoFixupProc     = UndoFixup;
  604.                 frPtr->fileState.windowCursorProc  = WindowCursor;
  605.                 frPtr->fileState.writeDocumentProc = WriteDocument;
  606.                 break;
  607.         }
  608.  
  609.         frPtr->fileState.windowSizeBounds.left   = kMinWindowWidth;
  610.         frPtr->fileState.windowSizeBounds.top    = kMinWindowHeight;
  611.         frPtr->fileState.windowSizeBounds.right  = kMaxWindowWidth;
  612.         frPtr->fileState.windowSizeBounds.bottom = kMaxWindowHeight;
  613.             /* Default min/max window size for growIcon. */
  614.  
  615.         pstr = frPtr->fileState.fss.name;
  616.         pcpy(pstr, untitled);
  617.         if (pstr[0]) {
  618.             if (incTitleNum)
  619.                 ++untitledCount;
  620.             pcatdec(pstr, untitledCount);
  621.                 /* Create the default document title.  It is stored in the FSSpec,
  622.                 ** as we can't place it in the window title.  We don't have a window
  623.                 ** yet to "title".  This will happen later.  The title is gotten from
  624.                 ** the FSSpec, so we are effectively done. */
  625.         }
  626.  
  627. •••        err = InitDocument(frHndl);
  628.             /* Call the application for any additional document initialization.
  629.             ** Other handles may need to be allocated.  The default values above
  630.             ** may be incorrect for certain document types.  This gives the
  631.             ** application a chance to change any defaults that are incorrect. */
  632.  
  633.         if ((*frHndl)->fileState.sfType == MovieFileType) {
  634.             movie = NewMovie(newMovieActive);
  635.             err   = GetMoviesError();
  636.             if (!err) {
  637.                 ClearMovieChanged(movie);
  638.                 (*frHndl)->fileState.movie = movie;
  639.             }
  640.         }
  641.  
  642.         if (err) {
  643.             DisposeHandle((Handle)frHndl);
  644.             if (returnHndl)
  645.                 *returnHndl = nil;
  646.                     /* If the application couldn't complete the document
  647.                     ** initialization, pitch the handle. */
  648.         }
  649.     }
  650.  
  651.     return(err);
  652. }
  653.  
  654.  
  655. Note the two lines marked with •••.  These two points the framework function
  656. NewDocument() calls the application, which are:
  657.  
  658. 1) The call to InitDocumentSize()
  659. 2) The call to InitDocument()
  660.  
  661.  
  662. These two functions are part of your application.  (They are in the source file
  663. File.c)  The first is called just so NewDocument() knows how big a handle to
  664. create for the document reference.  If your application has only one document
  665. type, then this function will simply return a constant.  If your application
  666. has multiple document types, you will want to return the size that corresponds
  667. to the document type (OSType).
  668.  
  669. Your application's function InitDocumentSize() is passed the OSType.  At this
  670. point, the framework has only this piece of information on the document-to-be.
  671. Until the application framework knows the size, it can't create the file
  672. reference handle.  You tell the application framework how big to create the
  673. frHndl.  It then creates it to the requested size, and initializes it to 0's and
  674. default values.
  675.  
  676. Once it is initialized to defaults, NewDocument() calls your application, giving
  677. it a chance to change the default values.  Only the frHndl is passed to
  678. InitDocument().  The frHndl from this point on is the document reference.
  679.  
  680. If you have different document types, you will have to have a reasonably smart
  681. InitDocument() function.  It will have to look at the document type, and then
  682. adjust the default values for the frHndl based on the document type.  At this
  683. point, the document type is stored in the frHndl, in the field fileState.sfType.
  684.  
  685. All of this is done prior to a window being created for the frHndl.  This is
  686. important, as a number of the procedure pointers determine how the window will
  687. be created.  You need to make customizations to the frHndl after its creation,
  688. and prior to the creation of the document's window.
  689.  
  690. Of course, if your application only has one document type, then these defaults
  691. are just fine, and you won't have to worry about all of this stuff.
  692.  
  693. The NewDocument() is pretty big, but that means that you don't have to worry
  694. about this stuff.  The NewDocument() function is in charge of creating a
  695. functioning frHndl.  The only thing your application has to do is to say how
  696. big it should be, and what defaults you didn't like.
  697.  
  698.  
  699. Let's revisit the code for a "New" menu item choice:
  700.  
  701.  
  702.         err = NewDocument(&frHndl, gAppWindowType, true);
  703.         if (!err) {
  704.             err = DoNewWindow(frHndl, nil, FrontWindow(), (WindowPtr)-1);
  705.             if (err)
  706.                 DisposeDocument(frHndl);
  707.         }
  708.         if (err)
  709.             CenteredAlert(rErrorAlert, nil, (ModalFilterProcPtr)AlertFilter);
  710.         break;
  711.  
  712.  
  713. We have explored what happens when NewDocument() is called.  All that remains
  714. is the DoNewWindow() call.
  715.  
  716.  
  717. A window is opened for the frHndl.  The values stored in the frHndl are used
  718. to determine how to open the window.
  719.  
  720. In the sample call above, we create a window for the document frHndl.  Here's
  721. what the parameters indicate:
  722.  
  723. nil:            Don't bother returning a reference to the window created.
  724.                 If we cared, we might pass in something like &newWindow.
  725. FrontWindow():    Window is created on the same monitor that holds most of the
  726.                 current front window.  
  727. (WindowPtr)-1:    Window is created as the front-most window.
  728.  
  729.  
  730. That's all there is to it.  Now all you need is a way to hold your document
  731. data.
  732.  
  733.  
  734. Interestingly enough, DTS.framework has plenty of document support.  You can
  735. use the hierarchical document architecture built into DTS.framework.  It is
  736. very powerful.  It handles file I/O, hierarchically related data, infinite
  737. undo, etc.
  738.  
  739.  
  740. The hierarchical document architecture is the default type of document.  If
  741. you choose to use it, there are already calls supplied to create the initial
  742. default document.  All you have to do is to call DefaultInitDocument() in
  743. your application's InitDocument() function.  For all documents that use the
  744. hierarchical document architecture, simply call DefaultInitDocument() from
  745. within InitDocument().
  746.  
  747. What DefaultInitDocument does is it creates a root object of type TreeObjHndl
  748. and places it in an expected location within the frHndl for the document.
  749. Consider this as a document within a document.  The frHndl describes the
  750. run-time aspects of the document.  The root object in the hierarchy begins
  751. the portion of the document that "persists", that is, saves to disk.
  752.  
  753. The root object is just that.  Yes, it has a data portion to it, but the main
  754. purpose of the root object is a holder of the "child" objects of the document.
  755.  
  756. Whenever you make a change to the document, you will make it to some number of
  757. objects with the hierarchy.  There are specific calls for this, and if you stick
  758. to these calls, infinite undo works automatically.  There is a separate document
  759. describing the hierarchical document architecture.  You will find it in the
  760. DTS.Lib folder.  It is called "=Using TreeObj.c".  It will detail everything
  761. you need to know about using the hierarchical document package.
  762.  
  763. Let's review the chain of information for a document, startiong with the window:
  764.  
  765. window:        References the document (frHndl) associated with the window.
  766.             The reference to the frHndl is kept in the window's refcon field.
  767.  
  768. frHndl:        References back to the window.  This means that if you have either
  769.             the window or the frHndl for a document, you can easily get the
  770.             other.
  771.  
  772.             Also references the root hierarchy object.  This is true only if you
  773.             elect to use the TreeObj package in the DTS.framework.  DTS.Draw
  774.             uses this package heavily.  Wannabe also uses it, but has no objects
  775.             defined, as Wannabe is only an application shell.
  776.  
  777.  
  778. root:        References the frHndl that contains it.
  779.  
  780.             Also references the undo root object that contains all of the
  781.             information necessary to undo/redo edits to the document.
  782.  
  783.             References all of the children added to the root object.
  784.  
  785.  
  786. child:        References all children it may in turn have.
  787.  
  788.             References the parent object.  The root object can also be
  789.             considered a child, but it is a child with no parent.  This
  790.             is reflected in the parent reference.  If the parent reference
  791.             is nil, then the object is a root object.
  792.  
  793.  
  794. This is a nice top-down view of where document/window information is stored.
  795. Here's some more information:
  796.  
  797. frHndl information is NOT saved with the document.  Any information you wish
  798. to be saved with the document must be contained in a hierarchy object.
  799.  
  800. Changes to the root object can't be undone.  The location in the document
  801. that is automatically kept for undo/redo operations is referenced by parent
  802. and child number.  This locates you to any object within the hierarchy,
  803. except for one.  The root object has no parent, so the location of the root
  804. object can not be indicated by parent/childNum.
  805.  
  806. This means:
  807.  
  808. • Any information that is run-time, and you DON'T want saved to disk should be
  809.   kept directly in the frHndl.
  810. • Any information that you want to keep with the document, but you don't want
  811.   involved in undo/redo operations should be kept in the root object.
  812. • Any information that you want to be able to save and undo/redo in the
  813.   document should be kept in a child below the level of the root object.
  814.  
  815.  
  816. Of course, all of the rules from the root object down imply that you are using
  817. the hierarchical document architecture.  This is a completely optional package.
  818. All other rules continue to apply, whether or not you are using this package.
  819.  
  820.  
  821.  
  822.  
  823. With the exception of the root object, all of the above information is kept in
  824. the fileState portion of an frHndl.  The root object begins the actual document,
  825. so it is actually in the document portion of an frHndl.  Why am I talking about
  826. frHndl portions?  Because an frHndl is comprised of three distinct structures.
  827. They are:
  828.  
  829. FileStateRec:    This is most adequately covered above.
  830. ConnectRec:        This hasn't even been mentioned.
  831. TheDoc:            This has been hinted at.
  832.  
  833.  
  834. The ConnectRec portion of a document record looks like this:
  835.  
  836. typedef struct {
  837.     long            windowID[2];        /* Used to match up windows. */
  838.     short            endSendInfo;        /* Above is send info.         */
  839.  
  840.     Boolean            connected;            /* Flag showing we are connected.      */
  841.     AEAddressDesc    remoteLoc;            /* AppleEvent address of remote user. */
  842.     Str32            remoteName;            /* Name of user connected to.          */
  843.     Str32            remoteZone;            /* Zone of user connected to.          */
  844.     Str32            remoteMachine;        /* Machine name of user connected to. */
  845.     short            endLocalInfo;        /* Above info is for 1 machine only.  */
  846. } ConnectRec;
  847.  
  848. The ConnectRec is used by DTS.framework to establish a connection to
  849. another application (or itself).  This connection links two specific windows.
  850. Once connected, the boolean 'connected' is set true, and the other fields
  851. are filled in.  The AEAddressDesc of whom you connected to is saved.  The name,
  852. zone, and machine name are also kept.  The endLocalInfo field is a reference
  853. point to determine the end of the ConnectRec at run-time.  This is here in case
  854. there are additional fields added in the future.  They would be added before the
  855. endLocalInfo field.  Since the ConnectRec structure is DTS.framework's
  856. responsibility to maintain, you don't have to worry about this.  (I do.)
  857.  
  858.  
  859. To see how to use the Apple Event facilities of DTS.framework, check out the
  860. application Wannabe.  It uses these facilities.
  861.  
  862.  
  863.  
  864. The final component of an frHndl is the document portion.  It looks like this:
  865.  
  866.  
  867. typedef struct {
  868.     DocHeaderInfo    fhInfo;  /* Doc hdr info (vers, prRec, window loc.) */
  869.     TreeObjHndl        root;
  870. } TheDoc;
  871.  
  872.  
  873. where DocHeaderInfo looks like this:
  874.  
  875.  
  876. typedef struct {
  877.     short        version;            /* The file format version.                   */
  878.     Boolean        printRecValid;        /* True if prRec has been created.           */
  879.     TPrint        print;                /* Print record for file.                   */
  880.     Rect        structureRect;        /* Remember where window was when saved.   */
  881.     Rect        contentRect;        /* Remember where window was when saved.   */
  882.     Rect        stdState;            /* This and below rect used for saving       */
  883.     Rect        userState;            /* zoom information for window.               */
  884.     long        hDocSize;            /* hDocSize and vDocSize have to be saved  */
  885.     long        vDocSize;            /* with the document, or recalculated       */
  886.                                     /* when the file is opened so that the       */
  887.                                     /* window can be created the correct size. */
  888.     short        endDocHeaderInfo;    /* End version, print, and window info.       */
  889. } DocHeaderInfo;
  890.  
  891.  
  892. I suppose you figured that the document portion was your domain.  Well, it is,
  893. kind of.  Since DTS.framework supplies hierarchical document support, it has
  894. to have access to this portion of the frHndl, as well.  You always need the
  895. fhInfo field of the document portion.  Also, it must be the first field in
  896. the structure.
  897.  
  898. Note the comment on the hDocSize and vDocSize fields.  You can instruct the
  899. framework to save the DocHeaderInfo structure automatically with the document,
  900. in either the data fork or the resource fork.  If you choose this option,
  901. then you don't have to worry about saving them or recalculating them.  You
  902. will still have to initialize them to something appropriate whenever you
  903. create a new document, however.
  904.  
  905. You optionally need the root field.  If you are using the hierarchical document
  906. package, then you must have this field, and it must be the second field.
  907.  
  908. Aside from these two rules, the structure is all yours.  You can add as much as
  909. you wish to it.
  910.  
  911.  
  912. As has been described above, a single frHndl consists of three components.
  913. These are:
  914.     1) FileStateRec
  915.     2) ConnectRec
  916.     3) The document record.
  917.  
  918. The way you assemble these parts into a single frHndl is like this:
  919.  
  920.  
  921. typedef struct FileRec {
  922.     FileStateRec    fileState;        /* DTS.Lib expects this structure here. */
  923.     ConnectRec        connect;        /* DTS.Lib expects this structure here. */
  924.     union {
  925.         TheDoc    doc;                /* Union in each document type here. */
  926.     } d;
  927. } FileRec;
  928.  
  929.  
  930. This combines the three documents parts into a single structure.  Note the use
  931. of union for the actual document portion.  This is because you can have multiple
  932. document types within your application.  Whenever you add a document type, just
  933. create a structure for it, and then union in this new structure into the FileRec
  934. definition.  That's all there is to it (sort of).
  935.  
  936. You have to remember to change the InitDocumentSize() and InitDocument()
  937. functions so that they can handle the new document type.  Once this is done,
  938. you now have a new document type added to your application.
  939.  
  940.  
  941. I think this should give you a good feel of what you need to first, second, etc.
  942. Any other questions about using DTS.Lib and Wannabe should be answered in other
  943. read.me files.  Of course, you can always look at the sample applications
  944. DTS.Draw and Wannabe for implementation-specific details.
  945.  
  946.  
  947.  
  948. NOTE!!    The above is very good information as to how Wannabe and the application
  949.         framework actually function.  However, a programming utility has been
  950.         written that automates the above.  This utility is called AppsToGo.
  951.         Use it.  The heck with code.  AppsToGo allows you to use Wannabe and the
  952.         framework without having to code for a while.  Eventually you will have
  953.         prototyped all the functionality possible and you will have to write
  954.         some lines of code.  However, AppsToGo allows you to defer coding for
  955.         quite some time.
  956.         The changes to Wannabe that you make with AppsToGo are permanent, and
  957.         adding code to Wannabe doesn't prevent you form continuing to use
  958.         AppsToGo.  CHECK IT OUT!!
  959.  
  960.  
  961. Eric Soldan
  962.